import os
import string

from threading import Timer
from kivy.compat import string_types
from kivy.factory import Factory
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.carousel import Carousel
from kivy.uix.dropdown import DropDown
from kivy.uix.image import Image
from kivy.uix.filechooser import FileChooserIconView, FileChooserListView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.spinner import Spinner
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget

from dryfire.view.bgfgColorSelector import BGFGColorSelector
from dryfire.view.buttonLogView import ButtonLogView
from dryfire.model.sortedDictList import SortedDictList


class _Spinner(Spinner):
    """The user can touch the button instead of the dropdown to select the
    current selection.
    """
    def __init__(self, **kwargs):
        Spinner.__init__(self, **kwargs)

    def _build_dropdown(self, *largs):
        if self._dropdown:
            self._dropdown.unbind(on_select=self._on_dropdown_select)
            self._dropdown.unbind(on_dismiss=self.handle_on_dismiss)
            self._dropdown.dismiss()
            self._dropdown = None
        cls = self.dropdown_cls
        if isinstance(cls, string_types):
            cls = Factory.get(cls)
        self._dropdown = cls()
        self._dropdown.auto_dismiss = False
        self._dropdown.bind(on_select=self._on_dropdown_select)
        self._dropdown.bind(on_dismiss=self.handle_on_dismiss)
        self._update_dropdown()

    def _toggle_dropdown(self, *largs):
        self.is_open = not self.is_open
        
    def handle_on_dismiss(self, *largs):
        self.is_open = False            


class _FormLabel(Label):
    """A label with help text which appears below the label.
    """
    def __init__(self, text, helptext='', **kwargs):
        if len(helptext) > 0:
            kwargs['text'] = "[b]%s[/b]\n%s" % (text, helptext)
        else:
            kwargs['text'] = "[b]%s[/b]" % (text)
        kwargs['markup'] = True
        Label.__init__(self, **kwargs)
        self.bind(width=self.resize_text)
        
    def resize_text(self, *largs):
        self.text_size = (self.width, None)


class _TextInput(TextInput):
    """A text input with an on_text method.
    kwargs['on_text_handler'] is called with instance and value.
    See MessageView.edit_line.
    """
    def __init__(self, **kwargs):
        if 'on_text_handler' in kwargs:
            self._on_text_handler = kwargs['on_text_handler']
            del(kwargs['on_text_handler'])
        else:
            self._on_text_handler = None
        TextInput.__init__(self, **kwargs)
                                         
    def on_text(self, instance, value):
        if self._on_text_handler is not None:
            self._on_text_handler(instance, value) 


class MessageView(object):
    """A class with methods for forms and popups.
    
    public method list:
    .build_form_label: returns a label with help text.
    .edit_line: a popup for editing a line.
    .message_info: a popup with a titled message.
    .message_warn: a popup with a message titled 'warning'.
    .message_success: a popup with a message titled 'success'.
    .question_yes_no: a popup question with answers 'yes', 'no'.
    .select_slides: a popup for selecting a slide.
    .select_bgfg_colors: a popup for selecting bg and fg colors.
    .select_options: a popup for selecting a string option.
    .select_folder: a popup for selecting a folder.
    .select_file: a popup for selecting a file.
    """
    def __init__(self):
        self._messageview_popup = None
        self._messageview_command_handler = None
        self._messageview_command_handler_largs = ()
        self._messageview_back_handler = None
        self._messageview_reload_handler = None
        self._messageview_post_command_handler = None
        self._messageview_post_command_true = ''
        self._messageview_post_command_handler_largs = ()
        self._text_input = None
        self._spinner_input = None
        self._carousel_input = None
        self._autocomplete_dict = None
        self._autocomplete_list = None
        # metrics
        self._spacing = 10
        self._padding = 10
        self._popup_width = 600
        self._part_width = self._popup_width - (2 * self._padding)
        self._button_width = self._part_width
        self._text_width = self._part_width - 30
        self._part_height = 50
        self._carousel_parts = 4
        self._carousel_size = (dp(self._part_width - 3 * self._padding),
                               dp(self._part_height * self._carousel_parts))
        self._bgfg_parts = 6
        self._bgfg_size = (dp(self._part_width - 3 * self._padding),
                           dp(self._part_height * self._bgfg_parts))

    @ property    
    def message_carousel_size(self):
        return self._carousel_size

    # form label
        
    def build_form_label(self, text, helptext, **kwargs):
        """Makes a form label with additional help text.
        """
        return _FormLabel(text, helptext, **kwargs)
        
    def new_separator(self, **kwargs):
        kwargs['size']=(dp(self._part_width), dp(self._part_height))
        kwargs['source'] = 'atlas://data/images/defaulttheme/progressbar'
        kwargs['keep_ratio'] = False
        kwargs['allow_stretch'] = True
        return Image(**kwargs)

    # edit text popup

    def edit_line(self,
                  title,
                  prompt,
                  default_text, text_handler, *text_handler_largs,
                  **kwargs):
        """
        text_handler will receive text, *text_handler_largs
        autocomplete_dict=instance_of_SortedDictLIst
        """
        self._process_kwargs(**kwargs)
        self._autocomplete_dict = kwargs.get('autocomplete_dict', None)
        if not isinstance(self._autocomplete_dict, SortedDictList):
            self._autocomplete_dict = None
        self._messageview_command_handler = text_handler
        self._messageview_command_handler_largs = text_handler_largs
        # title
        spaces = 0
        parts = 1
        height = 0
        # prompt and help
        help = kwargs.get('help', '')
        if len(help) > 0:
            if len(prompt) > 0:
                prompt += '\n' + help
            else:
                prompt = help
        using_help = len(prompt) > 0
        if using_help:
            intro = Label(text=prompt,
                          text_size=(dp(self._text_width), None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1]
            spaces += 1
        # parts : edit_line, submit button, cancel button = 3
        spaces += 3
        parts += 3
        # autocomplete list
        if self._autocomplete_dict is not None:
            spaces += 1
            parts += 3
        # separator
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        self._text_input = _TextInput(text=default_text,
                                      multiline=False,
                                      on_text_validate=lambda *largs : None,
                                      size=(dp(self._part_width), dp(self._part_height)),
                                      on_text_handler=self.w_messageview_handle_text)
        layout.add_widget(self._text_input)
        # add the autocomplete widget
        if self._autocomplete_dict is not None:
            self._autocomplete_list = ButtonLogView(self.w_messageview_autocomplete_on_press,
                                                    50, # min button height
                                                    False, # scroll with adds
                                                    size_hint=(None, None),
                                                    size=(dp(self._part_width), dp(3 * self._part_height)))
            layout.add_widget(self._autocomplete_list)
        # add the separator
        separator = self.new_separator()
        layout.add_widget(separator)
        # buttons
        layout.add_widget(Button(text='Enter',
                                 on_press=self.w_messageview_text_submit_handler,
                                 size=(dp(self._button_width), dp(self._part_height))))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=(dp(self._button_width), dp(self._part_height))))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self._messageview_popup.dismiss,
                                 size=(dp(self._button_width), dp(self._part_height))))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    # message popups

    def message_info(self, title, message, **kwargs):
        """
        kwargs options:
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *command_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *command_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(400), dp(300)),
                                        auto_dismiss=False)
        self._process_kwargs(**kwargs)
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(message) > 0
        if using_help:
            intro = Label(text=message,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1]
            spaces += 1
        # ok button
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            parts += 1
            spaces += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=(dp(self._button_width), dp(self._part_height))))
        layout.add_widget(Button(text='Ok',
                                 on_press=self._messageview_popup.dismiss,
                                 size=(dp(self._button_width), dp(self._part_height))))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    def message_warn(self, message, **kwargs):
        self.message_info('Warning', message, **kwargs)
        
    def message_success(self, message, **kwargs):
        self.message_info('Success', message, **kwargs)

    # yes / no popup
        
    def question_yes_no(self, title, question, command_handler, *command_handler_largs, **kwargs):
        """
        kwargs options:
        question becomse 'help' in self.select_options
        see self.select_options
        
        command_handler is passed True/False, **kwargs
        """
        self._messageview_post_command_handler = command_handler
        self._messageview_post_command_true = 'Yes'
        self._messageview_post_command_handler_largs = command_handler_largs
        kwargs['help'] = question
        options = ['Yes', 'No'] 
        self.select_options(title,
                            options,
                            options[0],
                            self.w_messageview_boolean_popup_on_press,
                            **kwargs)

    # select popups

    def select_slides(self, title, slides, default_slide, command_handler, *command_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *command_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *command_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_command_handler = command_handler
        self._messageview_command_handler_largs = command_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
            spaces += 1
        # submit and cancel buttons
        spaces += 2
        parts += 2
        # prev next buttons for carousel
        spaces += 1
        parts += 1
        # Carousel
        spaces += 1
        parts += self._carousel_parts
        # separator
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        if self._messageview_reload_handler is not None:
            # reload button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # sizes
        unit_size = (dp(self._part_width), dp(self._part_height))
        half_unit_size = (dp(self._part_width/2), dp(self._part_height))

        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # the carousel and prev next buttons
        self._carousel_input = Carousel(size=self._carousel_size,
                                        size_hint=(None, None),
                                        loop=True,
                                        min_move=0.01)
        index = 0
        for i in slides:
            self._carousel_input.add_widget(i)
            if i is default_slide:
                self._carousel_input.index = index
            index += 1
        prev_next_layout = BoxLayout(orientation='horizontal',
                                     size=unit_size)
        prev_button = Button(text='Previous',
                             size=half_unit_size)
        prev_button.bind(on_press=self.w_messageview_carousel_previous_handler)
        next_button = Button(text='Next',
                             size=half_unit_size)
        next_button.bind(on_press=self.w_messageview_carousel_next_handler)
        prev_next_layout.add_widget(prev_button)
        prev_next_layout.add_widget(next_button)
        layout.add_widget(prev_next_layout)
        layout.add_widget(self._carousel_input)
        # add the separator
        separator = self.new_separator()
        layout.add_widget(separator)
        # add the submit button
        layout.add_widget(Button(text='Select',
                                 on_press=self.w_messageview_carousel_submit_handler,
                                 size=unit_size))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=unit_size))
        if self._messageview_reload_handler is not None:
            layout.add_widget(Button(text='Reload',
                                     on_press=self.w_messageview_reload_handler,
                                     size=unit_size))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self.w_messageview_carousel_popup_dismiss,
                                 size=unit_size))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    def select_bgfg_colors(self, title, default_bg, default_fg, command_handler, *command_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *command_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *command_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_command_handler = command_handler
        self._messageview_command_handler_largs = command_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
            spaces += 1
        # submit and cancel buttons
        spaces += 2
        parts += 2
        # color selector
        spaces += 1
        parts += self._bgfg_parts
        # separator
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        if self._messageview_reload_handler is not None:
            # reload button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # sizes
        unit_size = (dp(self._part_width), dp(self._part_height))

        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # the colors selector
        self._color_input = BGFGColorSelector(size=self._bgfg_size,
                                              size_hint=(None, None))
        self._color_input.bg_color = default_bg
        self._color_input.fg_color = default_fg
        layout.add_widget(self._color_input)
        # add the separator
        separator = self.new_separator()
        layout.add_widget(separator)
        # add the submit button
        layout.add_widget(Button(text='Select',
                                 on_press=self.w_messageview_color_submit_handler,
                                 size=unit_size))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=unit_size))
        if self._messageview_reload_handler is not None:
            layout.add_widget(Button(text='Reload',
                                     on_press=self.w_messageview_reload_handler,
                                     size=unit_size))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self.w_messageview_color_popup_dismiss,
                                 size=unit_size))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    def select_options(self, title, commands, default, command_handler, *command_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *command_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *command_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_command_handler = command_handler
        self._messageview_command_handler_largs = command_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
            spaces += 1
        # separator
        spaces += 1
        parts += 1
        # submit and cancel buttons
        spaces += 2
        parts += 2
        # spinner
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        if self._messageview_reload_handler is not None:
            # reload button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # the command list
        self._spinner_input = _Spinner(text=default,
                                       values=commands,
                                       size_hint=(None, None),
                                       size=(dp(self._part_width-(2 * self._padding)), dp(self._part_height)))
        self._spinner_input.bind(is_open=self.handle_spinner_is_open)
        layout.add_widget(self._spinner_input)
        # add the separator
        separator = self.new_separator()
        layout.add_widget(separator)
        # add the submit button
        self._submit_button = Button(text='Select',
                                     on_press=self.w_messageview_spinner_submit_handler,
                                     size=(dp(self._button_width), dp(self._part_height)))
        layout.add_widget(self._submit_button)
        if self._messageview_back_handler is not None:
            self._back_button = Button(text='Back',
                                       on_press=self.w_messageview_back_handler,
                                       size=(dp(self._button_width), dp(self._part_height)))
            layout.add_widget(self._back_button)
        else:
            self._back_button = None
        if self._messageview_reload_handler is not None:
            self._reload_button = Button(text='Reload',
                                         on_press=self.w_messageview_reload_handler,
                                         size=(dp(self._button_width), dp(self._part_height)))
            layout.add_widget(self._reload_button)
        else:
            self._reload_button = None
        self._cancel_button = Button(text='Cancel',
                                     on_press=self._messageview_popup.dismiss,
                                     size=(dp(self._button_width), dp(self._part_height)))
        layout.add_widget(self._cancel_button)
        self._messageview_popup.content = layout
        self._messageview_popup.open()
    
    def handle_spinner_is_open(self, instance, value):
        if self._back_button is not None:
            self._back_button.disabled = value
        if self._reload_button is not None:
            self._reload_button.disabled = value
        if self._cancel_button is not None:
            self._cancel_button.disabled = value
        if self._submit_button is not None:
            self._submit_button.disabled = value

    def select_folder(self, title, root_path, path_handler, *path_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *path_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *path_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_command_handler = path_handler
        self._messageview_command_handler_largs = path_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
        # parts: 3 for path, select path, cancel
        #        4 for the scrolling command list
        spaces += 4
        parts += 7
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        if self._messageview_reload_handler is not None:
            # reload button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # file chooser
        self.fc = FileChooserListView(rootpath=root_path,
                                      size_hint=(None, None),
                                      size=(dp(self._part_width-(2 * self._padding)), dp(4 * self._part_height)))
        layout.add_widget(self.fc)
        # path label
        self.path = Label(text=root_path,
                          valign='middle',
                          text_size=(dp(self._text_width), dp(self._part_height)),
                          size=(dp(self._text_width), dp(self._part_height)),
                          size_hint=(None, None))
        layout.add_widget(self.path)
        self.fc.bind(path=self.w_messageview_set_path_from_fc)
        # buttons
        layout.add_widget(Button(text='Select Current Path',
                                 on_press=self.w_messageview_select_fc_path,
                                 size=(dp(self._part_width), dp(self._part_height))))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=(dp(self._part_width), dp(self._part_height))))
        if self._messageview_reload_handler is not None:
            layout.add_widget(Button(text='Reload',
                                     on_press=self.w_messageview_reload_handler,
                                     size=(dp(self._part_width), dp(self._part_height))))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self._messageview_popup.dismiss,
                                 size=(dp(self._part_width), dp(self._part_height))))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    def select_file(self, title, filters, root_path, path_handler, *path_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *path_handler_largs
            * if back_handler is provided then a back button will be displayed
        reload_handler=my_reload_callback:
            * is the optional reload button callback
            * the callback will only be sent params *path_handler_largs
            * if reaload_handler is provided then a reload button will be displayed
        """
        self._messageview_command_handler = path_handler
        self._messageview_command_handler_largs = path_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # height
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
            spaces += 1
        # parts: 3 for path, select file, cancel
        #        4 for the scrolling command list
        spaces += 4
        parts = 7
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        if self._messageview_reload_handler is not None:
            # reload button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # file chooser
        self.fc = FileChooserListView(rootpath=root_path,
                                      filters=filters,
                                      size_hint=(None, None),
                                      size=(dp(self._part_width-(2 * self._padding)), dp(4 * self._part_height)))
        layout.add_widget(self.fc)
        # path label
        self.path = Label(text=root_path,
                          valign='middle',
                          text_size=(dp(self._text_width), dp(self._part_height)),
                          size=(dp(self._text_width), dp(self._part_height)),
                          size_hint=(None, None))
        layout.add_widget(self.path)
        self.fc.bind(path=self.w_messageview_set_path_from_fc)
        # buttons
        layout.add_widget(Button(text='Select Current File',
                                 on_press=self.w_messageview_select_fc_file,
                                 size=(dp(self._part_width), dp(self._part_height))))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=(dp(self._part_width), dp(self._part_height))))
        if self._messageview_reload_handler is not None:
            layout.add_widget(Button(text='Reload',
                                     on_press=self.w_messageview_reload_handler,
                                     size=(dp(self._part_width), dp(self._part_height))))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self._messageview_popup.dismiss,
                                 size=(dp(self._part_width), dp(self._part_height))))
        self._messageview_popup.content = layout
        self._messageview_popup.open()
        
    # widget methods

    def w_messageview_autocomplete_on_press(self, instance):
        # put the value in self._text_input.text
        self._text_input.text = instance.text
        
    def w_messageview_handle_text(self, instance, value):
        if self._autocomplete_dict is not None:
            # using the dictionary
            hint_list = self._build_hint_list(value)
            self._autocomplete_list.vm_clear_log()
            if len(hint_list) > 0:
                for i in hint_list:
                    self._autocomplete_list.vm_log(i)

    def w_messageview_boolean_popup_on_press(self, text):
        self._messageview_post_command_handler(text == self._messageview_post_command_true,
                                               *self._messageview_post_command_handler_largs)

    def w_messageview_set_path_from_fc(self, instance, value):
        self.path.text = value

    def w_messageview_button_on_press(self, instance):
        self._messageview_popup.dismiss()
        text = instance.text
        self._messageview_popup = None
        self._messageview_command_handler(instance.text, *self._messageview_command_handler_largs)

    def w_messageview_text_submit_handler(self, instance):
        text = self._text_input.text
        self._text_input = None
        self._messageview_popup.dismiss()
        self._messageview_popup = None
        self._messageview_command_handler(text, *self._messageview_command_handler_largs)

    def w_messageview_spinner_submit_handler(self, instance):
        text = self._spinner_input.text
        self._spinner_input = None
        self._messageview_popup.dismiss()
        self._messageview_popup = None
        self._messageview_command_handler(text, *self._messageview_command_handler_largs)

    def w_messageview_carousel_submit_handler(self, instance):
        slide = self._carousel_input.current_slide
        self.w_messageview_carousel_popup_dismiss()
        self._messageview_command_handler(slide, *self._messageview_command_handler_largs)

    def w_messageview_color_submit_handler(self, instance):
        bg = self._color_input.bg_color
        fg = self._color_input.fg_color
        self.w_messageview_color_popup_dismiss()
        self._messageview_command_handler(bg, fg, *self._messageview_command_handler_largs)

    def w_messageview_carousel_popup_dismiss(self, *largs):
        # *largs is not used
        # todo: remove individual widgets and binds
        self._carousel_input.clear_widgets()
        self._messageview_popup.dismiss()
        self._carousel_input = None
        self._messageview_popup = None
        
    def w_messageview_carousel_previous_handler(self, *largs):
        # *largs is ignored
        self._carousel_input.load_previous() 
        
    def w_messageview_carousel_next_handler(self, *largs):
        # *largs is ignored
        self._carousel_input.load_next() 

    def w_messageview_color_popup_dismiss(self, *largs):
        # *largs is not used
        # todo: remove individual widgets and binds
        # self._color_input.clear_widgets()
        self._messageview_popup.dismiss()
        self._color_input = None
        self._messageview_popup = None

    def w_messageview_back_handler(self, instance):
        self._messageview_popup.dismiss()
        self._messageview_popup = None
        self._messageview_back_handler(*self._messageview_command_handler_largs)

    def w_messageview_reload_handler(self, instance):
        self._messageview_popup.dismiss()
        self._messageview_popup = None
        self._messageview_reload_handler(*self._messageview_command_handler_largs)

    def w_messageview_select_fc_path(self, instance):
        self._messageview_popup.dismiss()
        path = self.fc.path
        self._messageview_popup = None
        self._messageview_command_handler(path, *self._messageview_command_handler_largs)

    def w_messageview_select_fc_file(self, instance):
        if len(self.fc.selection) == 1:
            self._messageview_popup.dismiss()
            path = os.path.join(self.fc.path, self.fc.selection[0])
            self._messageview_popup = None
            self._messageview_command_handler(path, *self._messageview_command_handler_largs)

    # private methods
        
    def _process_kwargs(self, **kwargs):
        self._messageview_back_handler = kwargs.get('back_handler', None)
        self._messageview_reload_handler = kwargs.get('reload_handler', None)

    def _build_hint_list(self, value):
        hint_list = []
        if len(value) > 0:
            if value[-1] not in string.punctuation:
                key = self._autocomplete_dict.make_key(value)
                names = self._autocomplete_dict.get_sorted_names(key)
                if names is not None:
                    have_first = False
                    v = value.lower()
                    for i in names:
                        if i.lower().startswith(v):
                            hint_list.append(i)
                            have_first = True
                        else:
                            if have_first is True:
                                break
        return hint_list
